#include "Task.hpp"
#include <string>
#include <vector>
#include <unistd.h>
#include <cstdlib>
#include <assert.h>
#include <iostream>
#include <ctime>
#include <sys/stat.h>
#include <sys/wait.h>
const int processnum =5;
std::vector<task_t> tasks;


//对管道--先描述
struct channel
{
    int _cmdfd;              //-----发送任务的文件描述符----不是很理解
    pid_t _slaverid;         //子进程的PID
    std::string _processname;//子进程的名字-方便打印日志

    channel(int cmdfd,pid_t slaverid,const std::string processname)
    :_cmdfd(cmdfd)
    ,_slaverid(slaverid)
    ,_processname(processname)
    {}
};
void slaver()
{
    //父进程向管道写入固定大小的文件(4字节),读也读4字节
    while(true)
    {
        int cmdcode=0;
        int n=read(0,&cmdcode,sizeof(int));//读取任务码
        //--------------如果父进程不写入,会阻塞在read?
        if(n==sizeof(int))
        {
            //执行cmdcode对应的任务列表
            std::cout<<getpid()<<": cmdcode : "<<cmdcode<<std::endl;
            if(cmdcode >= 0 && cmdcode<tasks.size())
            {
                tasks[cmdcode]();
            }
        }
        if(n==0) break;
    }
}
//理解


//传参规范问题
//输入:cosnt &
//输出: *
//输入输出:&
void InitProcessPool(std::vector<channel>& channels)
{
    for(int i=0;i<processnum;i++)
    {
        //创建管道
        int pipefd[2]={0};
        int n=pipe(pipefd);//---------pipe的返回值
        assert(!n);//---------------assert的判断条件
        (void)n;//-----------------?
        pid_t id =fork();
        if(id==0)
        {
            //子进程---读取
            close(pipefd[1]);
            //slaver(pipefd[0]);//任务函数
            dup2(pipefd[0],0);
            close(pipefd[0]);
            slaver();
            std::cout<<"process:"<<getpid()<<"quit"<<std::endl;
            exit(0);//-------------避免子进程执行后续代码

        }
        //父进程---写入
        close(pipefd[0]);
        //构建channel
        std::string name="process-"+std::to_string(i);
        channels.push_back(channel(pipefd[1],id,name));
    }
}
//补充1:for循环,第一次父进程开34,关闭3.第二次父进程开35关闭3.类推
//此时的3就成了所以子进程的公共的读端
//但是注意:3不一定是公共的读端-复杂的进程,文件很多

//设计slaver(pipefd[0])成不传参,使用dup2将公共的读端重定向到0
//理解0原来是键盘文件,现在成了公共的读端
//当使用read系统调用时!当使用read系统调用时!当使用read系统调用时!
//原来从键盘中等待输入,现在从公共的读端进行读取
//---目的只是为了slaver不传参,直接从0读,弱化管道的概念----设计而已


void Debug(const std::vector<channel>& channels)
{
    for(auto &e : channels)
    {
        std::cout<<e._cmdfd<<" "<<e._slaverid<<" "<<e._processname<<std::endl;
    }
}

void ctrlSlaver(const std::vector<channel>& channels)
{
    for(int i=0;i<10;i++)
    {
        //1.选择任务
        int cmdcode =rand()%tasks.size();//--------产生任务编号
        //2.选择进程---负载均衡.每一个进程尽可能平均分配到同样多的任务--随机/轮询
        int processpos=rand()%channels.size();//-----通过channels的下标选择
        std::cout <<"already sent:"<<channels[processpos]._slaverid<<std::endl;
        //3.发送任务
        write(channels[processpos]._cmdfd,&cmdcode,sizeof(cmdcode));
        sleep(1);
    }
}


void QuitProcess(const std::vector<channel>& channels)
{
    for(const auto& e:channels)
    {
        close(e._cmdfd);
    }
    sleep(5);
    for(const auto& e:channels)
    {
        pid_t rid=waitpid(e._slaverid,nullptr,0);
    }
    sleep(5);
}

int main()
{
    //创建任务列表
    LoadTask(tasks);

    srand(time(nullptr)^1023);//产生随机数,按位与只是让他更随机
    //再组织
    std::vector<channel> channels;
    //1.初始化
    InitProcessPool(channels);
    //测试
    Debug(channels);
    //2.开始控制子进程
    ctrlSlaver(channels);

    //3.清理收尾
    QuitProcess(channels);
    return 0;
}

